#pragma once

#ifndef STUDIO_GEO_UTILS_H
#define STUDIO_GEO_UTILS_H

#include "geometry/studio_geo.h"
#include "geometry/studio_geo_coll.h"
// #include <log/studio_log.h>
#if ENABLE_GDAL
#include "gdal_priv.h"
#endif

// 矢量文件后缀名
#define STUDIO_SHP_SUFFIX ".shp"
#define STUDIO_TAB_SUFFIX ".tab"
#define STUDIO_GEOJSON_SUFFIX ".geojson"
#define STUDIO_SQLITE_SUFFIX ".sqlite"
#define STUDIO_CSV_SUFFIX ".csv"
#define STUDIO_KML_SUFFIX ".kml"
#define STUDIO_GML_SUFFIX ".gml"
#define STUDIO_XLSX_SUFFIX ".xlsx"

// 矢量文件存储格式
#define STUDIO_SHP_DRIVER_NAME "ESRI Shapefile"
#define STUDIO_TAB_DRIVER_NAME "Mapinfo File"
#define STUDIO_GEOJSON_DRIVER_NAME "GeoJSON"
#define STUDIO_SQLITE_DRIVER_NAME "SQLite"
#define STUDIO_CSV_DRIVER_NAME "CSV"
#define STUDIO_KML_DRIVER_NAME "KML"
#define STUDIO_GML_DRIVER_NAME "GML"
#define STUDIO_XLSX_DRIVER_NAME "XLSX"


class studio_geo_utils
{
public:
    enum ProjDef : int
    {
        WGS84 = 361,
        CGCS2000_GAUSS_E75 = 1075,
        CGCS2000_GAUSS_E78 = 1078,
        CGCS2000_GAUSS_E81 = 1081,
        CGCS2000_GAUSS_E84 = 1084,
        CGCS2000_GAUSS_E87 = 1087,
        CGCS2000_GAUSS_E90 = 1090,
        CGCS2000_GAUSS_E93 = 1093,
        CGCS2000_GAUSS_E96 = 1096,
        CGCS2000_GAUSS_E99 = 1099,
        CGCS2000_GAUSS_E102 = 1102,
        CGCS2000_GAUSS_E105 = 1105,
        CGCS2000_GAUSS_E108 = 1108,
        CGCS2000_GAUSS_E111 = 1111,
        CGCS2000_GAUSS_E114 = 1114,
        CGCS2000_GAUSS_E117 = 1117,
        CGCS2000_GAUSS_E120 = 1120,
        CGCS2000_GAUSS_E123 = 1123,
        CGCS2000_GAUSS_E126 = 1126,
        CGCS2000_GAUSS_E129 = 1129,
        CGCS2000_GAUSS_E132 = 1132,
        CGCS2000_GAUSS_E135 = 1135
    };

public:
    /// <summary>
    /// 初始化 GDAL 环境,只需要初始化一次
    /// </summary>
    static void init_gdal_env();

    /// <summary>
    /// 销毁 GDAL 环境, 一旦执行此方法,那么当前进程及其子线程中GDAL就无法使用
    /// </summary>
    static void destroy_gdal_env();

    /// <summary>
    /// 求一个面的形心(几何中心),利用ogr库算法
    /// </summary>
    /// <param name="poly"></param>
    /// <returns></returns>
    static studio_point poly_centroid(const studio_poly& poly);

    /// <summary>
    /// 求两个点的方位角,p2相对于p1的方位角(左上角右下角坐标系均可), 正北方向为0度,顺时针
    /// </summary>
    /// <param name="p1">参照物</param>
    /// <param name="p2">参照方向</param>
    /// <returns>p2相对于p1的方位角,结果为角度值,</returns>
    static double azimuth(studio_point from, studio_point to);

    /// <summary>
    /// 角度转方向 正北方向为0度, 顺时针
    /// </summary>
    /// <param name="angle"></param>
    /// <returns></returns>
    static std::string angle_to_desc(const double& angle);

    /// <summary>
    /// 读取矢量文件中的数据存储到studio_geo_coll数据结构中
    /// </summary>
    /// <param name="u8file">utf8</param>
    /// <param name="collection"></param>
    /// <returns></returns>
    /// 注:读取 shp , geojson 类型文件中可以实现
    static bool read_geo_coll(const std::string& u8file, std::vector<studio_geo_coll>& collections, const bool& ignore_prop = false);

    /// <summary>
    /// 将studio_geo_coll数据结构写入矢量文件(如shp文件)
    /// </summary>
    /// <param name="u8file">utf8</param>
    /// <param name="collection"></param>
    /// <returns></returns>
    /// 注:写入 shp , geojson 类型文件中经测试可以实现
    static bool write_geo_coll(const std::string& u8file, const std::vector<studio_geo_coll>& collections, const ProjDef& prj = WGS84);

    /**
     * 通用几何数据写入函数
     *
     * @tparam T 几何数据类型 (studio_point, studio_line, studio_polygon, std::vector<studio_point> 等)
     * @param file_path 输出文件路径
     * @param data 几何数据
     * @param geometry_type 几何类型枚举
     *
     * 此函数封装了将不同类型几何数据写入GeoJSON文件的通用逻辑
     */
    template <typename T>
    static void write_geo(const std::string& file_path, const T& data, enum_geometry_type geometry_type);


    /// <summary>
    /// 是否为一个标准的shp文件
    /// </summary>
    /// <param name="u8file">utf8</param>
    /// <returns></returns>
    static bool is_valid_shp(const std::string& u8file);

    /// <summary>
    /// 检查shp文件组中缺失的文件
    /// .shp: 存储几何矢量
    /// .dbf: 存储属性信息 (该文件不存在 gdal也能打开shp,成功读取矢量,但是无法读取属性信息)
    /// .shx: 存储几何矢量索引
    /// </summary>
    /// <param name="u8file"></param>
    /// <returns></returns>
    static std::vector<std::string> shp_missing_file(const std::string& u8file);

    /// <summary>
    /// 加载shp文件中的属性信息
    /// </summary>
    /// <param name="u8file">utf8</param>
    /// <param name="type"></param>
    /// <param name="properties"></param>
    /// <returns></returns>
    static bool check_shp_info(const std::string& u8file, enum_geometry_type& type, std::map<std::string, studio_geo_prop::enum_prop_type>& properties);

    /// <summary>
    /// 根据文件类型得到对应的 gdal 中的存储格式
    /// 支持的文件格式对应的存储类型: shp tab geojson sqlite csv kml gml xlsx
    /// </summary>
    /// <param name="u8file">文件名</param>
    /// <param name="driverName">存储类型</param>
    /// <returns></returns>
    static bool get_driver_name(const std::string& u8file, std::string& driverName);

    /// <summary>
    /// 矢量与面是否相交
    /// </summary>
    /// <param name=""></param>
    /// <param name=""></param>
    /// <returns></returns>
    static bool intersect(const studio_multi_poly& mpoly1, const studio_multi_poly& mpoly2);

    /// <summary>
    /// 点是否与矢量面相交(射线算法),点的相交在面内或者在边界上都算是相交
    /// </summary>
    /// <param name="mpoly"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    static bool intersect(const studio_poly& mpoly, const studio_point& point);

    /// <summary>
    /// 点是否与矢量多面是否相交(射线算法),点的相交在面内或者在边界上都算是相交
    /// </summary>
    /// <param name="mpoly"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    static bool intersect(const studio_multi_poly& mpoly, const studio_point& point);

    static bool intersect(const studio_multi_poly& mpoly, const studio_line& line);

    static bool intersect(const studio_point& point, const std::vector<studio_point>& points);

    /// <summary>
    /// 点距离线在一定范围内
    /// </summary>
    /// <param name="point"></param>
    /// <param name="line"></param>
    /// <param name="dist"></param>
    /// <returns></returns>
    static bool nearby(const studio_point& point, const studio_line& line, const double& dist);

    /// <summary>
    /// 两个面的相交区域
    /// </summary>
    /// <param name="mpoly1"></param>
    /// <param name="mpoly2"></param>
    /// <returns></returns>
    static std::vector<studio_poly> intersection(const studio_multi_poly& mpoly1, const studio_multi_poly& mpoly2);

    /// <summary>
    /// 两个线段相交点
    /// </summary>
    /// <param name="s1"></param>
    /// <param name="s2"></param>
    /// <returns></returns>
    static std::optional<studio_point> intersection(const studio_segment& s1, const studio_segment& s2);

    /// <summary>
    /// 两个相交面的 不相交部分, 必须是相交的
    /// </summary>
    /// <param name="mpoly1"></param>
    /// <param name="mpoly2"></param>
    /// <returns></returns>
    static std::vector<studio_poly> trans_intersection(const studio_multi_poly& mpoly1, const studio_multi_poly& mpoly2);

    /// <summary>
    /// 线相交面的部分
    /// </summary>
    /// <param name="mpoly1"></param>
    /// <param name="line"></param>
    /// <returns></returns>
    static std::vector<studio_line> intersection(const studio_multi_poly& mpoly, const studio_line& line);

    /// <summary>
    /// 穿过面的线, 不再面内的部分
    /// </summary>
    /// <param name="mpoly1"></param>
    /// <param name="mpoly2"></param>
    /// <returns></returns>
    static std::vector<studio_line> trans_intersection(const studio_multi_poly& mpoly1, const studio_line& line);

    /// <summary>
    /// 距离,直接数值计算
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    static double distance(const studio_point& p1, const studio_point& p2);

    /// <summary>
    /// 距离的平方,直接数值计算
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    static double distance_sq(const studio_point& p1, const studio_point& p2);

    /// <summary>
    /// 距离,经纬度转换为千米计算,Vincenty公式,
    /// 相较于墨卡托投影方法, 跨带也能正常使用
    /// https://github.com/atychang/geo-distance/blob/master/vincenty/cpp/CalcDistance.cc
    /// http://www.movable-type.co.uk/scripts/latlong-vincenty.html
    /// https://en.wikipedia.org/wiki/Vincenty's_formulae
    /// </summary>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    static double distance_km(const studio_point& p1, const studio_point& p2);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="points"></param>
    /// <returns></returns>
    static double area(const std::vector<studio_point>& points);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="xs"></param>
    /// <param name="ys"></param>
    /// <returns></returns>
    template <typename T>
    static double area(const std::vector<T>& xs, const std::vector<T>& ys);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="pnum">点数量</param>
    /// <param name="xs"></param>
    /// <param name="ys"></param>
    /// <returns></returns>
    template <typename T>
    // typename std::enable_if<std::is_arithmetic<T>::value, T>::type
    static double area(const int& pnum, const T* xs, const T* ys);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="pnum">点数量</param>
    /// <param name="points">x0,y0,x1,y1 .... xn,yn n = pnum - 1</param>
    /// <returns></returns>
    template <typename T>
    static double area(const int& pnum, const T* points);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="mpoly"></param>
    /// <returns></returns>
    static double area(const studio_poly& poly);

    /// <summary>
    /// 计算面积平方公里
    /// </summary>
    /// <param name="points">经纬度点</param>
    /// <returns></returns>
    static double area_sqkm(const std::vector<studio_point>& points);

    /// <summary>
    /// 计算面积平方公里
    /// </summary>
    /// <param name="mpoly">经纬度面</param>
    /// <returns></returns>
    static double area_sqkm(const studio_poly& poly);

    /// <summary>
    /// 面积
    /// </summary>
    /// <param name="mpoly"></param>
    /// <returns></returns>
    static double area(const studio_multi_poly& mpoly);

    /// <summary>
    /// 计算面积平方公里
    /// </summary>
    /// <param name="mpoly">经纬度面</param>
    /// <returns></returns>
    static double area_sqkm(const studio_multi_poly& mpoly);

    /// <summary>
    /// 平滑线
    /// </summary>
    /// <param name="line"></param>
    /// <param name="mod">二次贝塞尔 或者 三次贝塞尔</param>
    /// <param name="interp">插值</param>
    /// <returns></returns>
    static std::vector<studio_point> smooth_line(const std::vector<studio_point>& line, const int& mod, const int& interp);

    /// <summary>
    /// 平滑闭合环
    /// </summary>
    /// <param name="line"></param>
    /// <param name="mod">二次贝塞尔 或者 三次贝塞尔 或者其他</param>
    /// <param name="interp">插值</param>
    /// <returns></returns>
    static std::vector<studio_point> smooth_ring(const std::vector<studio_point>& ring, const int& mod, const int& interp);

    /// <summary>
    /// 简化线,使用的道格拉斯抽稀算法,可以尽量保持矢量特征
    /// </summary>
    /// <param name="line"></param>
    /// <param name="dist">距离容差</param>
    /// <returns></returns>
    static std::vector<studio_point> simplify_line(const std::vector<studio_point>& line, const double& dist);

    /// <summary>
    /// 简化闭合环
    /// </summary>
    /// <param name="line"></param>
    /// <param name="dist">距离容差</param>
    /// <returns></returns>
    static std::vector<studio_point> simplify_ring(const std::vector<studio_point>& ring, const double& dist);

    /// <summary>
    /// 创建缓冲区
    /// </summary>
    /// <param name="coll"></param>
    /// <param name="distance">缓冲区大小(正负均可),要求coll中矢量的单位和distance单位保持一致</param>
    /// <returns></returns>
    static studio_geo_coll buffer(const studio_geo_coll& coll, const double& distance);

#if ENABLE_GDAL

    static OGRSpatialReference get_proj_def(const ProjDef& def);

    /// <summary>
    /// 读取shp文件
    /// </summary>
    /// <param name="u8file"></param>
    /// <param name="collections"></param>
    /// <param name="ignore_prop"></param>
    /// <returns></returns>
    /// ================ gdal中矢量与silly utils中矢量互转 ================

    /// ================ 单点 ================

    /// <summary>
    /// 将 OGRPoint(单点) 转换为 studio_point(单点) 类型
    /// </summary>
    /// <param name="ogrPoint"></param>
    /// <returns></returns>
    static studio_point studio_point_from_ogr(const OGRPoint* ogrPoint);

    /// <summary>
    /// 将 studio_point(单点) 转换为 OGRPoint(单点) 类型
    /// </summary>
    /// <param name="point"></param>
    /// <returns></returns>
    static OGRPoint studio_point_to_ogr(const studio_point& point);

    /// ================ 多点 ================

    /// <summary>
    /// 将 OGRMultiPoint(多点) 转换为 studio_multi_point(多点) 类型
    /// </summary>
    /// <param name="ogrMultiPoint"></param>
    /// <returns></returns>
    static studio_multi_point studio_multi_point_from_ogr(const OGRMultiPoint* ogrMultiPoint);

    /// <summary>
    /// 将 studio_multi_point(多点) 转换为 OGRMultiPoint(多点) 类型
    /// </summary>
    /// <param name="multiPoint"></param>
    /// <returns></returns>
    static OGRMultiPoint studio_multi_point_to_ogr(const studio_multi_point& multiPoint);

    /// ================ 单线 ================

    /// <summary>
    /// OGRLineString(线)类型转为studio_line(线)类型
    /// </summary>
    /// <param name="lineString"></param>
    /// <returns></returns>
    static studio_line studio_line_from_ogr(const OGRLineString* lineString);

    /// <summary>
    /// 将 studio_line(线) 转换为 OGRLineString(线)类型
    /// </summary>
    /// <param name="line"></param>
    /// <returns></returns>
    static OGRLineString studio_line_to_ogr(const studio_line& line);

    /// ================ 多线 ================

    /// <summary>
    /// OGRMultiLineString(多线)类型转为 studio_multiline(多线)类型
    /// </summary>
    /// <param name="multiLineString"></param>
    /// <returns></returns>
    static studio_multi_line studio_multi_line_from_ogr(const OGRMultiLineString* multiLineString);

    /// <summary>
    /// 将 studio_multiline(多线) 转换为 OGRMultiLineString(多线)类型
    /// </summary>
    /// <param name="multiLine"></param>
    /// <returns></returns>
    static OGRMultiLineString studio_multi_line_to_ogr(const studio_multi_line& multiLine);

    /// ================ 闭合环 ================

    /// <summary>
    /// 环OGRLinearRing对象，将其转换为studio_ring对象  (环)
    /// </summary>
    /// <param name="ring"></param>
    /// <returns></returns>
    static studio_ring studio_ring_from_ogr(const OGRLinearRing* ring);

    /// <summary>
    /// 将 studio_ring 转换为 OGRPolygon
    /// </summary>
    /// <param name="ring"></param>
    /// <returns></returns>
    static OGRLinearRing studio_ring_to_ogr(const studio_ring& ring);

    /// ================ 单面 ================

    /// <summary>
    /// OGRPolygon 对象转换为 studio_poly (多环:外环+内环)对象  (单面)
    /// </summary>
    /// <param name="polygon"></param>
    /// <returns></returns>
    static studio_poly studio_poly_from_ogr(const OGRPolygon* polygon);

    /// <summary>
    /// 将 studio_poly 转换为 OGRPolygon(单面)
    /// </summary>
    /// <param name="poly"></param>
    /// <returns></returns>
    static OGRPolygon studio_poly_to_ogr(const studio_poly& poly);

    /// ================ 多面 ================

    /// <summary>
    /// 多面的OGRMultiPolygon对象转换为studio_multi_poly(多面)
    /// </summary>
    /// <param name="multiPolygon"></param>
    /// <returns></returns>
    static studio_multi_poly studio_multi_poly_from_ogr(const OGRMultiPolygon* multiPolygon);

    /// <summary>
    /// 将studio_multi_poly对象转换为OGRMultiPolygon对象(多面)
    /// </summary>
    /// <param name="multiPoly"></param>
    /// <returns></returns>
    static OGRMultiPolygon studio_multi_poly_to_ogr(const studio_multi_poly& multiPoly);

    /// ================ OGRGeometry ================

    /// <summary>
    /// 将 OGRGeometry 对象转换为studio_geo_coll对象,
    /// OGRGeometry是一个抽象类，无法实例化,只能以指针的方式使用,
    /// 注意: 返回的对象需要手动释放,释放方法:
    /// if (OGRGeometry* != nullptr)
    /// {
    ///    OGRGeometryFactory::destroyGeometry(OGRGeometry*);
    ///    OGRGeometry* = nullptr;
    /// }
    /// </summary>
    /// <param name="coll"></param>
    /// <returns></returns>
    static OGRGeometry* studio_geo_coll_to_ogr(const studio_geo_coll& coll);

    /// <summary>
    /// 将studio_geo_coll对象转换为OGRGeometry对象
    /// </summary>
    /// <param name="geometry"></param>
    /// <returns></returns>
    static studio_geo_coll studio_geo_coll_from_ogr(const OGRGeometry* geometry);
#endif
};

template <typename T>
double studio_geo_utils::area(const std::vector<T>& xs, const std::vector<T>& ys)
{
    double result = 0.0;
    if (xs.size() != ys.size())
    {
        return result;
    }
    size_t pnum = xs.size();
    // 确保至少有3个点才能构成一个多边形
    if (pnum < 3)
    {
        return result;
    }

    for (int i = 0; i < pnum; ++i)
    {
        int j = (i + 1) % pnum;
        result += xs[i] * ys[j];
        result -= xs[j] * ys[i];
    }
    return std::abs(result) / 2.0;
}

template <typename T>
double studio_geo_utils::area(const int& pnum, const T* points)
{
    double result = 0.0;
    // 确保至少有3个点才能构成一个多边形
    if (pnum < 3)
    {
        return result;
    }

    for (int n = 0; n < pnum; ++n)
    {
        int i = n * 2;
        int j = ((n + 1) % pnum) * 2;
        result += points[i] * points[j + 1];
        result -= points[j] * points[i + 1];
    }
    return std::abs(result) / 2.0;
}

template <typename T>
double studio_geo_utils::area(const int& pnum, const T* xs, const T* ys)
{
    double result = 0.0;
    // 确保至少有3个点才能构成一个多边形
    if (pnum < 3)
    {
        return result;
    }

    for (int i = 0; i < pnum; ++i)
    {
        size_t j = (i + 1) % pnum;
        result += xs[i] * ys[j];
        result -= xs[j] * ys[i];
    }
    return std::abs(result) / 2.0;
}


template <typename T>
void studio_geo_utils::write_geo(const std::string& file_path, const T& data, enum_geometry_type geometry_type)
{
    std::vector<studio_geo_coll> collections;
    studio_geo_coll coll;
    coll.m_type = geometry_type;

    if constexpr (std::is_same_v<T, studio_point>) // 单点
    {
        coll.m_point = {data};
    }
    else if constexpr (std::is_same_v<T, studio_multi_point>) // 多点
    {
        coll.m_points = data;
    }
    else if constexpr (std::is_same_v<T, studio_line>) // 单线
    {
        coll.m_line = data;
    }
    else if constexpr (std::is_same_v<T, studio_multi_line>) // 多线
    {
        coll.m_lines = data;
    }
    else if constexpr (std::is_same_v<T, studio_poly>) // 单面
    {
        coll.m_poly = data;
    }
    else if constexpr (std::is_same_v<T, studio_multi_poly>) // 多面
    {
        coll.m_polys = data;
    }
    else
    {
        std::cout << "Error: Unknown data type." << std::endl;
        return;
    }

    collections.push_back(coll);
    studio_geo_utils::write_geo_coll(file_path, collections);
}


typedef studio_geo_utils geo_utils; // 兼容之前的写法

#endif  // STUDIO_GEO_UTILS_H